/***************************************************************************
Copyright (c) 2018, Xilinx, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors 
may be used to endorse or promote products derived from this software 
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 ***************************************************************************/

#ifndef _XF_HOG_DESCRIPTOR_NORM_HPP_
#define _XF_HOG_DESCRIPTOR_NORM_HPP_

#ifndef __cplusplus
#error C++ is needed to include this header
#endif

#define 	XF_CLIP_VAL 		13108		//  Q0.2 in Q0.16 format
#define 	XF_MAX_VAL_16_BIT	65535
#define     XF_3_P_6            921         //  Q2.8 format


/*******************************************************************************
 * 						 xFDHOGnormalizeKernel
 *******************************************************************************
 *  This function performs normalization and computes the partial
 *  renormalization factor.
 *
 *  HA arrays: contains binned data from the histogram computation function (I)
 *
 *  ssv arrays: contains sum of squared data from the histogram computation
 *  		function (I)
 *
 *  norm_block: holds the normalized data (O)
 *
 *  partial_rnf_sum: contains the partial ssv value for Re-normalization (O)
 *
 *  bj : index for horizontal blocks
 *
 *******************************************************************************/
template < typename ssv_type, typename  tmp_nf_sq24_type>
void xFDHOGnormalizeKernel1 (ssv_type ssv_1, ssv_type ssv_2,  uint16_t bj,
		tmp_nf_sq24_type& tmp_nf_sq24)
{
#pragma HLS INLINE OFF

	// temporary arrays to hold the ssv values of 4 cells
	ap_uint<46> tmp_nf_1, tmp_nf_2;

	tmp_nf_1 = ssv_1[bj] + ssv_2[bj];
	tmp_nf_2 = ssv_1[bj+1] + ssv_2[bj+1];
	ap_ufixed<47,47,AP_TRN,AP_SAT> tmp_nf_sum;

	// contains the added up ssv values Q31.16 format
	tmp_nf_sum = (tmp_nf_1 + tmp_nf_2);

	// after square root the format is Q16.8
	tmp_nf_sq24 = xFSqrtHOG<24>(tmp_nf_sum) + XF_3_P_6;
}


template < typename tmp_nf_sq24_type, typename  norm_fact_type>
void xFDHOGnormalizeKernelInv (tmp_nf_sq24_type tmp_nf_sq24,
		norm_fact_type& nf, char& n)
{
	int m = 16;
	// after inverse the format will be Q(32-n).n
	nf = xFInverse24(tmp_nf_sq24,m,&n);
}

template < int NOHC, int NOHCPB, int NOVCPB, typename hist_type, int NOB,
typename norm_block_type, typename fx_rnf_sq_type, typename norm_fact_type>
void xFDHOGnormalizeKernel2 (hist_type HA_1[][NOB], hist_type HA_2[][NOB], norm_fact_type nf,
		norm_block_type* norm_block, fx_rnf_sq_type& fx_rnf_sq, uint16_t bj, char n)
{
#pragma HLS INLINE OFF

	// HA_1 nad HA_2 in the Q15.8 format
	uint32_t tmp_clip_1, tmp_clip_2;

	// offsets to index the norm_array
	uchar_t offset_1[2], offset_2[2];
#pragma HLS ARRAY_PARTITION variable=offset_1 complete dim=1
#pragma HLS ARRAY_PARTITION variable=offset_2 complete dim=1

	offset_1[0] = 0;	offset_1[1] = NOB<<1;
	offset_2[0] = NOB;	offset_2[1] = (NOB<<1)+NOB;

	/* keeping the clip value and norm factor in
	  		the same format in the same format  */
	uint16_t fx_clip_val = XF_CLIP_VAL;		// 0.2 taken in Q0.16 format
	ap_uint<16> i,j;

	ap_uint<33> rnf_sum = 0;
	norm_loop:
	for( j = 0; j < NOHCPB; j++)
	{
		num_of_bins_loop:
		for( i = 0; i < NOB; i++)
		{
#pragma HLS LOOP_FLATTEN
#pragma HLS PIPELINE

			// normalization I
			// // (Q15.8 x Q(32-n).n) >> n-8 -> Q16.16 format
			tmp_clip_1 = (((HA_1[bj+j][i]) * nf) >> (n-8));
			tmp_clip_2 = (((HA_2[bj+j][i]) * nf) >> (n-8));

			// clipping
			if(tmp_clip_1 > fx_clip_val)
				tmp_clip_1 = fx_clip_val;

			if(tmp_clip_2 > fx_clip_val)
				tmp_clip_2 = fx_clip_val;

			// norm_block format -> Q0.16
			norm_block[offset_1[j]+i] = tmp_clip_1;
			norm_block[offset_2[j]+i] = tmp_clip_2;

			// rnf_sum -> Q0.32
			rnf_sum += (norm_block[offset_1[j]+i] * norm_block[offset_1[j]+i]) +
					(norm_block[offset_2[j]+i] * norm_block[offset_2[j]+i]);
		}
	}

	ap_ufixed<33,33,AP_TRN,AP_SAT> fx_rnf_sum = rnf_sum;

	ap_uint17_t fx_rnf_sq_2 = (ap_uint17_t)(xFSqrtHOG<17>(fx_rnf_sum)); // Q1.16 format result
	fx_rnf_sq = fx_rnf_sq_2 >> 1; // Q1.15 format (to take in 16 bits)
}


/*****************************************************************************
 * 						 xFDHOGReNormalizeKernel
 *****************************************************************************
 *  This function performs the renormalization operation
 *
 *  norm_block: normalized block data (I)
 *
 *  partial_rnf_sum: temporary variable for re-normalization (I)
 *
 *  _block_strm: output mat containing the normalized data (O)
 *
 *****************************************************************************/

template < int NOHC, int NOHCPB, int NOVCPB, int NOB, int ROWS, int COLS, int DEPTH,
int NPC, int WORDWIDTH, typename norm_block_type, typename fx_rnf_sq_type >
void xFDHOGReNormalizeKernel(norm_block_type* norm_block, fx_rnf_sq_type fx_rnf_sq,
		hls::stream<XF_SNAME(WORDWIDTH)>& _block_strm)
{
#pragma HLS INLINE OFF

	char n_rnf;
	uint32_t rnf = xf::Inverse(fx_rnf_sq,1,&n_rnf); 	// output in Q(32-n_rnf).n_rnf format

	XF_SNAME(WORDWIDTH) block_data;
	uchar_t step = XF_PIXELDEPTH(DEPTH); ap_uint<10> offset = 0;

	renorm_loop2:
	for(uchar_t k = 0; k < (NOB*NOHCPB*NOVCPB); k++)
	{
#pragma HLS PIPELINE

		ap_uint32_t tmp_block_data = (norm_block[k] * rnf) >> n_rnf;  // output in format Q0.16

		// to take care of the MSBs
		if(tmp_block_data > XF_MAX_VAL_16_BIT)
			tmp_block_data = XF_MAX_VAL_16_BIT;

		// packing the data to the output variable
		block_data.range(offset+(step-1),offset) = tmp_block_data;
		offset += step;
	}

	_block_strm.write(block_data);
}


/*****************************************************************************
 * 				xFDHOGNormalize
 *****************************************************************************
 * This function acts as a wrapper function for normalize and renormalize 
 * functions
 *
 * Inputs - HA arrays, ssv arrays and bi index
 *
 * Outputs - _block_strm (stream)
 *
 *****************************************************************************/
template <int ROWS, int COLS, int DEPTH, int NPC, int WORDWIDTH,
int NOHC, int NOHCPB, int NOVCPB, int NOHW, int NOVW, int NODPB,
int WIN_HEIGHT, int WIN_WIDTH, int CELL_HEIGHT, int CELL_WIDTH,
int NOHB,int NOB,typename hist_type ,typename ssv_type>
void xFDHOGNormalize( hist_type HA_1[][NOB], hist_type HA_2[][NOB],
		ssv_type* ssv_1, ssv_type* ssv_2,
		hls::stream<XF_SNAME(WORDWIDTH)>& _block_strm, uint16_t bi, uint16_t nohb, uint16_t nohc)
{
	// number of horizontal block index
	uint16_t bj = 0;

	ap_uint<24> tmp_nf_sq24_1[1], tmp_nf_sq24_2[1];
	uint32_t nf_1[1], nf_2[1];
	char n_1[1], n_2[1];
#pragma HLS RESOURCE variable=nf_1 core=RAM_1P_LUTRAM
#pragma HLS RESOURCE variable=nf_2 core=RAM_1P_LUTRAM
#pragma HLS RESOURCE variable=tmp_nf_sq24_1 core=RAM_1P_LUTRAM
#pragma HLS RESOURCE variable=tmp_nf_sq24_2 core=RAM_1P_LUTRAM
#pragma HLS RESOURCE variable=n_1 core=RAM_1P_LUTRAM
#pragma HLS RESOURCE variable=n_2 core=RAM_1P_LUTRAM


	// taking each bin as 16-bit unsigned type
	uint16_t norm_block_1[NODPB], norm_block_2[NODPB];
#pragma HLS ARRAY_PARTITION variable=norm_block_1 complete dim=1
#pragma HLS ARRAY_PARTITION variable=norm_block_2 complete dim=1

	// temporary variable for the renormalization (to hold the sum value)
	uint16_t fx_rnf_sq_1[1], fx_rnf_sq_2[1];

	bool flag = 1;
	xFDHOGnormalizeKernel1 (ssv_1,ssv_2,bj,tmp_nf_sq24_1[0]);

	bj++;
	xFDHOGnormalizeKernel1 (ssv_1,ssv_2,bj,tmp_nf_sq24_2[0]);
	xFDHOGnormalizeKernelInv(tmp_nf_sq24_1[0],nf_1[0],n_1[0]);

	bj++;
	xFDHOGnormalizeKernel1 (ssv_1,ssv_2,bj,tmp_nf_sq24_1[0]);
	xFDHOGnormalizeKernelInv(tmp_nf_sq24_2[0],nf_2[0],n_2[0]);
	xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_1[0],norm_block_1,
			fx_rnf_sq_1[0],bj-2,n_1[0]);

	no_of_HB:
	for(bj = 3; bj < nohb; bj++)
	{
#pragma HLS LOOP_TRIPCOUNT min=NOHB max=NOHB

		if(flag)
		{
			xFDHOGnormalizeKernel1 (ssv_1,ssv_2,bj,tmp_nf_sq24_2[0]);
			xFDHOGnormalizeKernelInv(tmp_nf_sq24_1[0],nf_1[0],n_1[0]);
			xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_2[0],norm_block_2,
					fx_rnf_sq_2[0],bj-2,n_2[0]);
			xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
			(norm_block_1,fx_rnf_sq_1[0],_block_strm);
			flag = 0;
		}

		else
		{
			xFDHOGnormalizeKernel1 (ssv_1,ssv_2,bj,tmp_nf_sq24_1[0]);
			xFDHOGnormalizeKernelInv(tmp_nf_sq24_2[0],nf_2[0],n_2[0]);
			xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_1[0],norm_block_1,
					fx_rnf_sq_1[0],bj-2,n_1[0]);
			xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
			(norm_block_2,fx_rnf_sq_2[0],_block_strm);
			flag = 1;
		}
	}

	if(flag)
	{
		xFDHOGnormalizeKernelInv(tmp_nf_sq24_1[0],nf_1[0],n_1[0]);
		xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_2[0],norm_block_2,
				fx_rnf_sq_2[0],bj-2,n_2[0]);
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
		(norm_block_1,fx_rnf_sq_1[0],_block_strm);
		flag = 0;
		bj++;
	}

	else
	{
		xFDHOGnormalizeKernelInv(tmp_nf_sq24_2[0],nf_2[0],n_2[0]);
		xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_1[0],norm_block_1,
				fx_rnf_sq_1[0],bj-2,n_1[0]);
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
		(norm_block_2,fx_rnf_sq_2[0],_block_strm);
		flag = 1;
		bj++;
	}

	if(flag)
	{
		xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_2[0],norm_block_2,
				fx_rnf_sq_2[0],bj-2,n_2[0]);
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
		(norm_block_1,fx_rnf_sq_1[0],_block_strm);
		flag = 0;
	}

	else
	{
		xFDHOGnormalizeKernel2 < NOHC,NOHCPB,NOVCPB > (HA_1,HA_2,nf_1[0],norm_block_1,
				fx_rnf_sq_1[0],bj-2,n_1[0]);
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH >
		(norm_block_2,fx_rnf_sq_2[0],_block_strm);
		flag = 1;
	}

	if(flag)
	{
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH > (norm_block_1,
				fx_rnf_sq_1[0],_block_strm);
	}
	else
	{
		xFDHOGReNormalizeKernel < NOHC,NOHCPB,NOVCPB,NOB,ROWS,COLS,DEPTH,NPC,WORDWIDTH > (norm_block_2,
				fx_rnf_sq_2[0],_block_strm);
	}
}

#endif   // _XF_HOG_DESCRIPTOR_NORM_HPP_


